library(dplyr)
library(ggplot2)
library(ggrepel)
library(knitr)
library(lubridate)
library(plotly)
library(purrr)
library(rrricanes)
library(stringr)
library(tibble)
library(tidyr)
Per the National Weather Service in Houston, TX, the following data are preliminary results from Hurricane Harvey:
THE DATA SHOWN HERE ARE PRELIMINARY….AND SUBJECT TO UPDATES AND CORRECTIONS AS APPROPRIATE.
THIS REPORT INCLUDES EVENTS OCCURRING WHEN WATCHES AND/OR WARNINGS WERE IN EFFECT…OR WHEN SIGNIFICANT FLOODING ASSOCIATED WITH HARVEY OR ITS REMNANTS WAS AFFECTING THE AREA.
COUNTIES INCLUDED…MADISON…MADISON…WALKER…BRAZOS…GRIMES…MONTGOMERY…SAN JACINTO…POLK…WASHINGTON…BURLESON…WALLER…HARRIS…LIBERTY…AUSTIN…COLORADO…WHARTON…FORT BEND…JACKSON…MATAGORDA…BRAZORIA…GALVESTON…CHAMBERS…TRINITY
# Load raw data
rpt <- "https://nwschat.weather.gov/p.php?pid=201709062030-KHGX-ACUS74-PSHHGX"
txt <- readLines(rpt)
# Set base plot
bp <- al_tracking_chart(color = "black", fill = "white", size = 0.1, res = 50)
## Regions defined for each Polygons
## Regions defined for each Polygons
Lowest SLP, Maximum Sustained Winds and Gusts
# subset section A
sec_a <- txt[grep("^A\\. LOWEST SEA LEVEL PRESSURE", txt):(grep("^B\\. MARINE OBSERVATIONS", txt) - 1)]
# trim head
sec_a <- sec_a[-c(1:9)]
# trim tail
sec_a <- head(sec_a, n = -4L)
# subset section B
sec_b <- txt[grep("^B\\. MARINE OBSERVATIONS", txt):(grep("^C\\. STORM TOTAL RAINFALL", txt) - 1)]
# trim head
sec_b <- sec_b[-c(1:8)]
# trim tail
sec_b <- head(sec_b, n = -5L)
# Bind to sec
sec <- c(sec_a, sec_b)
# There is a subheader in middle I'll also manually remove
sec <- sec[-c(78:88)]
# Get station header data
stations <- str_match(sec,
sprintf("^%s[\\s-]*?%s[\\s-]*?%s[\\s]+$",
"([[K|X|J|4]\\w]{3,5})", # Station
"([\\w\\s/\\.\\(\\)]+)", # Location
"([\\D]{2})")) %>% # State
.[complete.cases(.),]
# Get station obs. I really wanted to expand the regex to do this; use it as
# a learning opportunity. But, apparently my skills aren't quite that
# advanced... oh, well
obs <- str_match(sec, "^(\\d{2}\\.\\d{2}.+$)")[,2] %>% .[complete.cases(.)]
# Move to dataframe
slp <- as_data_frame(
list("Station" = stations[,2],
"Location" = stations[,3],
"State" = stations[,4],
"Lat" = as.numeric(str_sub(obs, 0L, 5L)),
"Lon" = as.numeric(str_sub(obs, 7L, 12L)),
"MinPres" = as.numeric(str_sub(obs, 16L, 21L)),
"MinPresDt" = as.POSIXct(ymd_hm(sprintf("2017-08-%s %s",
str_sub(obs, 23L, 24L),
str_sub(obs, 26L, 29L))),
tz = "UTC"),
"Remarks" = str_sub(obs, 31L, 31L),
"MaxSustDir" = as.numeric(str_sub(obs, 33L, 35L)),
"MaxSustSpd" = as.numeric(str_sub(obs, 37L, 39L)),
"MaxSustDt" = as.POSIXct(ymd_hm(sprintf("2017-08-%s %s",
str_sub(obs, 42L, 43L),
str_sub(obs, 45L, 48L))),
tz = "UTC"),
"PeakGustDir" = as.numeric(str_sub(obs, 52L, 54L)),
"PeakGustSpd" = as.numeric(str_sub(obs, 56L, 58L)),
"PeakGustDt" = as.POSIXct(ymd_hm(sprintf("2017-08-%s %s",
str_sub(obs, 60L, 61L),
str_sub(obs, 63L, 66L))),
tz = "UTC"))) %>%
mutate(Lat = if_else(Lat < 0, Lat * -1, Lat),
Lon = if_else(Lon > 0, Lon * -1, Lon))
## Warning: 13 failed to parse.
## Warning: 1 failed to parse.
## Warning: 1 failed to parse.
# Clean up
slp$MinPres[slp$MinPres == 9999.0] <- NA
slp$MaxSustDir[slp$MaxSustDir == 999] <- NA
slp$MaxSustSpd[slp$MaxSustSpd == 999] <- NA
p <- bp +
geom_point(data = slp,
aes(x = Lon, y = Lat,
text = sprintf("%s\n%s, %s\n%s UTC",
Station,
Location,
State,
MaxSustDt),
size = MaxSustSpd, color = MaxSustSpd)) +
coord_equal(xlim = c(min(slp$Lon, na.rm = TRUE), max(slp$Lon, na.rm = TRUE)),
ylim = c(min(slp$Lat, na.rm = TRUE), max(slp$Lat, na.rm = TRUE)))
## Warning: Ignoring unknown aesthetics: text
ggplotly(p, tooltip = c("text", "size", "color"))
Storm Total Rainfall
Storm total rainfall from Aug. 25, 12:00 UTC (7:00 AM CDT) through Aug. 31 12:00 UTC (7:00 AM CDT)
rain_obs <- txt[grep("^C\\. STORM TOTAL RAINFALL", txt):(grep("^D\\. INLAND FLOODING", txt) - 1)]
# Drop header and tail data
rain_obs <- rain_obs[-c(1:6)]
rain_obs <- head(rain_obs, -2L)
# Drop empties
rain_obs <- rain_obs[rain_obs != ""]
# Here I'll get observations first
pos <- str_match(rain_obs, sprintf("^%s[\\s]+%s[\\s]?$",
"([\\d\\.]{5})",
"([-\\d\\.]{6})"))
# Remove NAs
pos <- pos[complete.cases(pos),]
# Get location details
loc <- str_match(rain_obs, sprintf("^%s[\\s]{3,}%s[\\s]{3,}%s[\\s]{3,}%s[\\s]+%s$",
"([\\w\\d\\s\\.\\-/]{1,29})", # Location
"([\\w\\s]{1,20})", # County
"([\\w\\d-=]*)", # ID
"([\\d\\.]{3,5})", # Rainfall
"(I*)")) %>% # Remarks
.[complete.cases(.),]
# Make df
rain <- as_data_frame(
list("Location" = loc[,2],
"County" = loc[,3],
"ID" = loc[,4],
"Rainfall" = as.numeric(loc[,5]),
"Remarks" = loc[,6],
"Lat" = as.numeric(pos[,2]),
"Lon" = as.numeric(pos[,3]))) %>%
mutate(Lat = if_else(Lat < 0, Lat * -1, Lat),
Lon = if_else(Lon > 0, Lon * -1, Lon))
p <- bp +
geom_point(data = rain,
aes(x = Lon, y = Lat,
text = sprintf("%s\n%s, %s",
ID,
Location,
County),
size = Rainfall, color = Rainfall)) +
coord_equal(xlim = c(min(rain$Lon, na.rm = TRUE), max(rain$Lon, na.rm = TRUE)),
ylim = c(min(rain$Lat, na.rm = TRUE), max(rain$Lat, na.rm = TRUE)))
## Warning: Ignoring unknown aesthetics: text
ggplotly(p, tooltip = c("text", "size", "color"))
## We recommend that you use the dev version of ggplot2 with `ggplotly()`
## Install it with: `devtools::install_github('hadley/ggplot2')`
Maximum Storm Surge and Storm Tide
tide_obs <- txt[grep("^E\\. MAXIMUM STORM SURGE AND STORM TIDE", txt):(grep("^F\\. TORNADOES", txt) - 1)]
# Trim the edges
tide_obs <- tide_obs[-c(1:7)]
tide_obs <- head(tide_obs, n = -5L)
tide_obs[tide_obs == ""] <- NA
tide_obs <- tide_obs[complete.cases(tide_obs)]
tide <- as_data_frame(
list("County" = str_trim(str_sub(tide_obs, 0L, 16L)),
"Location" = str_trim(str_sub(tide_obs, 18L, 32L)),
"Surge" = as.numeric(str_trim(str_sub(tide_obs, 34L, 39L))),
"Tide" = as.numeric(str_trim(str_sub(tide_obs, 42L, 46L))),
"DateTime" = as.POSIXct(ymd_hm(sprintf("2017-08-%s %s",
str_sub(tide_obs, 49L, 50L),
str_sub(tide_obs, 52L, 55L))),
tz = "UTC"),
"Erosion" = str_trim(str_sub(tide_obs, 58L, 64L))))
kable(tide, caption = "Storm Surge and Storm Tide Totals (ft)")
| County | Location | Surge | Tide | DateTime | Erosion |
|---|---|---|---|---|---|
| HARRIS G | MORGANS POINT | 3.88 | 3.53 | 2017-08-27 19:30:00 | UNKNOWN |
| HARRIS | LYNCHBURG LANDI | 7.70 | 7.27 | 2017-08-29 07:06:00 | UNKNOWN |
| HARRIS | MANCHESTER | 11.44 | 10.35 | 2017-08-29 03:18:00 | UNKNOWN |
| GALVESTON | HIGH ISLAND | 5.03 | 4.00 | 2017-08-31 03:06:00 | UNKNOWN |
| GALVESTON | ROLLOVER PASS | 3.61 | 2.99 | 2017-08-26 18:30:00 | UNKNOWN |
| GALVESTON | EAGLE POINT | 4.18 | 3.67 | 2017-08-29 02:24:00 | UNKNOWN |
| GALVESTON | GALVESTON BAY E | 2.73 | 2.58 | 2017-08-29 03:54:00 | UNKNOWN |
| GALVESTON | GALVESTON PIER | 3.76 | 3.57 | 2017-08-26 02:00:00 | UNKNOWN |
| GALVESTON | GALVESTON RAILR | 3.14 | 2.76 | 2017-08-29 16:06:00 | UNKNOWN |
| BRAZORIA | SAN LUIS PASS | 3.22 | 3.32 | 2017-08-26 00:42:00 | UNKNOWN |
| BRAZORIA | FREEPORT | 3.19 | 2.52 | 2017-08-25 22:00:00 | UNKNOWN |
| MATAGORDA | SARGENT | 3.19 | 2.97 | 2017-08-29 21:18:00 | UNKNOWN |
| MATAGORDA | MATAGORDA CITY | 3.27 | 3.12 | 2017-08-26 08:54:00 | UNKNOWN |
Tornadoes
tor_obs <- txt[grep("^F\\. TORNADOES", txt):(grep("^G\\. STORM IMPACTS BY COUNTY", txt) - 1)]
tor_obs <- tor_obs[-c(1:6)]
tor_obs <- head(tor_obs, n = -3L)
# county, time and tornado scale
loc <- str_match(tor_obs, "^([\\w\\d\\s]{28})\\s+([\\w\\s]{16})\\s+([\\d/]{7})\\s+(EF\\d)\\s+$") %>%
.[complete.cases(.),]
# lat, lon
pos <- str_match(tor_obs, "^([\\d\\.]{4,5})\\s+([-\\d\\.]{4,6})$") %>%
.[complete.cases(.),]
# Now, get the remarks. Subtract loc and pos to make it easier.
# Essentially, what I'm doing here is joining the vector together
# with a \t. Consecutive \t's indicate a break between remarks so
# make that a newline. Then split on \n and trim, return to vector.
rmks <- tor_obs[!(tor_obs %in% c(loc, pos))]
rmks <- str_c(rmks, collapse = "\t")
rmks <- str_replace_all(rmks, "\t\t+", "\n")
rmks <- str_replace_all(rmks, "\t", " ")
rmks <- str_split(rmks, "\n")
rmks <- map(rmks, str_trim, side = "both") %>% flatten_chr()
tors <- as_data_frame(
list("Location" = loc[,2],
"County" = loc[,3],
"Date" = as.POSIXct(ymd_hm(sprintf("2017-08-%s %s",
str_sub(loc[,4], 0L, 2L),
str_sub(loc[,4], 4L, 7L)))),
"Scale" = factor(loc[,5],
levels = c("EF0", "EF1", "EF2", "EF3", "EF4", "EF5"),
labels = c("EF0", "EF1", "EF2", "EF3", "EF4", "EF5")),
"Lat" = as.numeric(pos[,2]),
"Lon" = as.numeric(pos[,3]),
"Remarks" = rmks))
| Scale | Wind (mph) | Wind (kts) |
|---|---|---|
| EFO | 65-85 | 55-74 |
| EF1 | 86-110 | 75-96 |
| EF2 | 111-135 | 97-117 |
| EF3 | 136-165 | 118-143 |
| EF4 | 166-200 | 143-174 |
| EF5 | >200 | >175 |
tmp <- tors %>% filter(Lat > 0, Lon < 0) # Filter out missing values
p <- bp +
geom_point(data = tmp,
aes(x = Lon, y = Lat,
text = sprintf("%s",
str_wrap(Remarks)),
size = Scale, color = Scale)) +
coord_equal(xlim = c(min(tmp$Lon, na.rm = TRUE), max(tmp$Lon, na.rm = TRUE)),
ylim = c(min(tmp$Lat, na.rm = TRUE), max(tmp$Lat, na.rm = TRUE)))
## Warning: Ignoring unknown aesthetics: text
ggplotly(p, tooltip = c("text", "size", "color"))
## We recommend that you use the dev version of ggplot2 with `ggplotly()`
## Install it with: `devtools::install_github('hadley/ggplot2')`
## Warning: Using size for a discrete variable is not advised.
kable(tors, caption = "Tornadoes")
| Location | County | Date | Scale | Lat | Lon | Remarks |
|---|---|---|---|---|---|---|
| 9 E TIKI ISLAND | GALVESTON | 2017-08-25 19:22:00 | EF0 | 29.31 | -94.77 | PUBLIC REPORTS A FUNNEL CLOUD AND A METAL FENCE DAMAGED NEAR FERRY RD IN GALVESTON; TORNADO START TIME 1922 UTC END TIME 1924 CDT. |
| SARGENT | MATAGORDA | 2017-08-25 20:45:00 | EF0 | 28.83 | -95.66 | SHED AND FENCE DAMAGE REPORTED FROM A TORNADO IN SARGENT. |
| 9 E TIKI ISLAND | GALVESTON | 2017-08-25 20:47:00 | EF0 | 29.31 | -94.77 | TORNADO DAMAGED MCDONALDS SIGN AT SEAWALL BLVD AND BROADWAY AVE. |
| 2 E ANGLETON | BRAZORIA | 2017-08-26 04:17:00 | EF1 | 29.17 | -95.39 | BRAZORIA COUNTY LAW ENFORCEMENT POSSIBLE TORNADO DAMAGE… HOUSE OFF FOUNDATION COUNTY RD. 210 AND FM 523. TIME GIVEN WAS 1117 PM BUT STILL NEED TO VERIFY WITH RADAR, TIME DID NOT MATCH. |
| 4 W ARCOLA | FORT BEND | 2017-08-26 05:50:00 | EF1 | 29.49 | -95.53 | HOMES DAMAGE ON VIEUX CARRE MINOR INJURIES AND RESPONDING DEPUTY BLOWN OFF THE ROAD. |
| 6 E FULSHEAR | FORT BEND | 2017-08-26 07:00:00 | EF0 | 29.70 | -95.78 | ROOF DAMAGE REPORTED TO A HOME NEAR WESTPARK TOLLWAY AND THE GRAND PARKWAY. |
| 6 E FULSHEAR | FORT BEND | 2017-08-26 07:00:00 | EF0 | 29.70 | -95.78 | ROOF DAMAGE REPORT TO A HOME NEAR WESTPARK TOLLWAY AND THE GREEN PARKWAY. THE TIME ESTIMATED FROM RADAR AND BROADCAST MEDIA ESTIMATED AROUND 2AM. |
| 3 S MANVEL | BRAZORIA | 2017-08-26 07:30:00 | EF0 | 29.44 | -95.36 | REPORT CAME IN FROM THE MANVEL EOC REGARDING DAMAGED VEHICLES AND BUILDING DAMAGE. POSSIBLY OCCURED BETWEEN 215-245 AM CDT BUT NO COUPLET SEEN ON HGX NOR HOU RADAR DURING THAT TIME. |
| 1 WSW KATY | FORT BEND | 2017-08-26 10:20:00 | EF1 | 29.79 | -95.84 | SIGNIFICANT DAMAGE TO A RV…BOAT AND STORAGE FACILITY. |
| 4 SE HUMBLE | HARRIS | 2017-08-26 12:44:00 | EF1 | 29.95 | -95.23 | BROADCAST MEDIA REPORTING TORNADO TOUCHDOWN RESULTING IN ROOF…TREE..AND FENCE DAMAGE IN LAKESHORE SUBDIVISION |
| 4 SSW WELLBORN | BURLESON | 2017-08-26 13:05:00 | EF0 | 30.48 | -96.32 | TREE DAMAGE REPORTED NEAR OLYMPIA BUDDY ROAD. |
| 2 SW CYPRESS | HARRIS | 2017-08-26 21:08:00 | EF1 | 29.95 | -95.73 | TREES BLOWN OVER AND MINOR ROOF DAMAGE REPORTED NEAR BARKER CYPRESS AND TUCKERTON. |
| 2 SE CYPRESS | HARRIS | 2017-08-26 21:27:00 | EF1 | 29.95 | -95.67 | ANOTHER TORNADO CONFIRMED ON THE GROUND IN THE VICINITY OF HIGHWAY 290 AND BARKER CYPRESS. |
| 1 NNE EAST BERNARD | WHARTON | 2017-08-26 22:30:00 | EF0 | 29.54 | -96.06 | TREES DOWN SE-NW PATH… DAMAGED HOME AND HORSE TRAILER OVERTURNED. |
| 1 SW EAST BERNARD | FORT BEND | 2017-08-26 22:32:00 | EF1 | 29.52 | -96.06 | THE TORNADO BEGAN AS A WEAK EF-0 IN DOWNTOWN EAST BERNARD AND THEN TRAVELED NORTHWEST ACROSS ALT HWY 90 WHERE IT STRENGTHENED TO AN EF-1 SNAPPING SEVERAL LARGE MATURE OAK AND PECAN TREES. A HOUSE SUFFERED SIGNIFICANT BRICK FACADE DAMAGE TO ONE SIDE OF THE HOME. SEVERAL TREES WERE DOWNED AND/OR SNAPPED ALONG THE PATH. |
| 2 W IOWA COLONY | BRAZORIA | 2017-08-26 05:50:00 | EF0 | 0.00 | 0.00 | A STRONG EF-0 TORNADO STRUCK A FAIRLY NEW SUBDIVISION ALONG COUNTY ROAD 56 AND HIGHWAY 288. DAMAGE WAS MOSTLY CONFINED TO ROOFS… FENCES…AND SEVERAL TREES SNAPPED AND/OR DOWNED. |
| 2 W LIVERPOOL | BRAZORIA | 2017-08-26 04:28:00 | EF0 | 0.00 | 0.00 | AN EF-0 TORNADO TOOK DOWN 4 POWER POLES ON HIGHWAY 35 ALONG WITH SEVERAL TREES NEAR THE GULF COAST SPEEDWAY. THE TORNADO THEN TRAVELED ACROSS GENERALLY OPEN FIELD BEFORE DAMAGING SOME BARNS AND OUTBUILDINGS AS WELL AS TREES ON COUNTY ROAD 511. |
| 1 SSW DANBURY | BRAZORIA | 2017-08-26 02:44:00 | EF1 | 0.00 | 0.00 | THE TORNADO BEGAN IN DANBURY AND DAMAGED A BARN ALONG WITH SEVERAL TREES OFF OF COUNTY ROAD 207. THE TORNADO THEN CROSSED HIGHWAY 35 AND MOVED OVER AN OPEN FIELD. THE TORNADO THEN SNAPPED AND/OR DOWNED SEVERAL TREES ALONG COUNTY RD 45 BEFORE LIFTING AT THE CROCODILE ENCOUNTER ON COUNTY RD 48. |
| 3 W BAILEYS PRARIE | BRAZORIA | 2017-08-26 01:25:00 | EF0 | 0.00 | 0.00 | A HIGH-END EF-0 TORNADO TOUCHED DOWN JUST EAST OF WEST COLUMBIA DAMAGING NUMEROUS TREES…ROOFS…AND OUTBUILDINGS IN A NEIGHBORHOOD OFF OF HIGHWAY 35 AND RIVER ROAD. A BARN AND SEVERAL OUTBUILDINGS WERE ALSO DESTROYED ON THE EAST SIDE OF THE BRAZOS RIVER. |
| 1 SSE BRAZORIA | BRAZORIA | 2017-08-25 20:30:00 | EF0 | 0.00 | 0.00 | A BRIEF TORNADO CROSSED HWY 36 WITH ONE DOWNED POWERLINE AND SEVERAL TREES SNAPPED AND/OR DOWNED. |
| 17 SW JONES CREEK | MATAGORDA | 2017-08-25 21:14:00 | EF1 | 0.00 | 0.00 | A BRIEF YET STRONG TORNADO MOVED ONSHORE ALONG THE COAST IN SARGENT CAUSING SIGNIFICANT DAMAGE TO ONE HOME AS WELL AS OVERTURNING A MOTORHOME. NUMEROUS TREES WERE SNAPPED AND/OR DOWNED ALONG THE PATH AS WELL AS MINOR ROOF DAMAGE TO SEVERAL HOMES AND BUSINESSES. |
| 1 NW BACLIFF | GALVESTON | 2017-08-27 10:05:00 | EF0 | 29.50 | -94.99 | SHORT DAMAGE PATH. TREES DOWN…MINOR DAMGE TO ROOF. |
Session Info
pander::pander(sessionInfo())
R version 3.4.0 (2017-04-21)
**Platform:** x86_64-pc-linux-gnu (64-bit)
locale: LC_CTYPE=en_US.UTF-8, LC_NUMERIC=C, LC_TIME=en_US.UTF-8, LC_COLLATE=en_US.UTF-8, LC_MONETARY=en_US.UTF-8, LC_MESSAGES=C, LC_PAPER=en_US.UTF-8, LC_NAME=C, LC_ADDRESS=C, LC_TELEPHONE=C, LC_MEASUREMENT=en_US.UTF-8 and LC_IDENTIFICATION=C
attached base packages: methods, stats, graphics, grDevices, utils, datasets and base
other attached packages: bindrcpp(v.0.2), tidyr(v.0.6.3), tibble(v.1.3.3), stringr(v.1.2.0), rrricanes(v.0.2.0-6), purrr(v.0.2.2.2), plotly(v.4.7.1), lubridate(v.1.6.0), knitr(v.1.16), ggrepel(v.0.6.5), ggplot2(v.2.2.1) and dplyr(v.0.7.2)
loaded via a namespace (and not attached): Rcpp(v.0.12.12), rrricanesdata(v.0.0.1.4), highr(v.0.6), compiler(v.3.4.0), plyr(v.1.8.4), bindr(v.0.1), tools(v.3.4.0), digest(v.0.6.12), lattice(v.0.20-35), viridisLite(v.0.2.0), jsonlite(v.1.5), evaluate(v.0.10.1), gtable(v.0.2.0), pkgconfig(v.2.0.1), rlang(v.0.1.1), shiny(v.1.0.3), crosstalk(v.1.0.0), yaml(v.2.1.14), blogdown(v.0.0.56), rnaturalearthdata(v.0.1.0), httr(v.1.2.1), htmlwidgets(v.0.9), rprojroot(v.1.2), grid(v.3.4.0), glue(v.1.1.1), data.table(v.1.10.4), R6(v.2.2.2), rmarkdown(v.1.6), bookdown(v.0.4), sp(v.1.2-5), pander(v.0.6.0), magrittr(v.1.5), backports(v.1.1.0), scales(v.0.4.1), htmltools(v.0.3.6), assertthat(v.0.2.0), xtable(v.1.8-2), mime(v.0.5), colorspace(v.1.3-2), httpuv(v.1.3.5), labeling(v.0.3), stringi(v.1.1.5), lazyeval(v.0.2.0) and munsell(v.0.4.3)